package com.anteater.eattheant.view;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Pair;

import com.anteater.eattheant.R;
import com.anteater.eattheant.util.Stack;
import com.anteater.eattheant.view.Anthill.Ant;

public class Tongue extends Group {

	public static final float HEIGHT = Maze.HEIGHT;
	public static final float WIDTH = Maze.WIDTH;

	public interface MoveListener {
		public void notify(short i, short j);
	}

	
	private class TonguePiece extends Group {
		public static final float HEIGHT = 1.0f;
		public static final float WIDTH = 1.0f;

		public final short i;
		public final short j;
		public short orientation;

		private Square texture;
		private int texfiles[];

		public TonguePiece(short i, short j, short orientation) {
			super();
			this.i = i;
			this.j = j;
			this.orientation = orientation;

			bindFiles();
			
			texture = new Square(1.0f);
			texture.setTexture(new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
					1.0f, 1.0f }, loadTexture(gl));
			add(texture);
		}
		
		private void bindFiles() {
			texfiles = new int[32];
			texfiles[Tunnel.TOP] = R.drawable.tongue_b_64x64;
			texfiles[Tunnel.RIGHT] = R.drawable.tongue_l_64x64;
			texfiles[Tunnel.BOTTOM] = R.drawable.tongue_t_64x64;
			texfiles[Tunnel.LEFT] = R.drawable.tongue_r_64x64;
			texfiles[Tunnel.TOP | Tunnel.RIGHT] = R.drawable.tongue_tr_64x64;
			texfiles[Tunnel.TOP | Tunnel.BOTTOM] = R.drawable.tongue_tb_64x64;
			texfiles[Tunnel.TOP | Tunnel.LEFT] = R.drawable.tongue_tl_64x64;
			texfiles[Tunnel.RIGHT | Tunnel.BOTTOM] = R.drawable.tongue_rb_64x64;
			texfiles[Tunnel.RIGHT | Tunnel.LEFT] = R.drawable.tongue_rl_64x64;
			texfiles[Tunnel.BOTTOM | Tunnel.LEFT] = R.drawable.tongue_bl_64x64;
		}
		
		private int loadTexture(GL10 gl) {
			int texture[] = new int[1];

			gl.glGenTextures(1, texture, 0);

			Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
					texfiles[orientation]);

			gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[0]);

			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
					GL10.GL_NEAREST);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
					GL10.GL_NEAREST);

			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

			bitmap.recycle();

			return texture[0];
		}

		public void setOrientation(short orientation) {
			this.orientation = orientation;
			texture.setTexture(new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
					1.0f, 1.0f }, loadTexture(gl));
		}
	}

	private final Maze maze;
	private Stack<TonguePiece> pieces;
	private MoveListener moveListener;
	private MoveListener collisionListener;
	private List<Pair<Integer, Ant>> deadAnts;
	private short score;

	private GL10 gl;
	private Context context;
	
	public Tongue(Maze maze, GL10 gl, Context context) {
		this.maze = maze;
		this.gl = gl;
		this.context = context;
		
		pieces = new Stack<TonguePiece>(maze.getGraph().length
				* maze.getGraph()[0].length);
		pieces.push(new TonguePiece((short) 0, (short) 0,
				(short) (Tunnel.TOP | Tunnel.BOTTOM)));
		score = 0;
	}

	public void setMoveListener(MoveListener moveListener) {
		this.moveListener = moveListener;
	}

	public void setCollisionListener(MoveListener collisionListener) {
		this.collisionListener = collisionListener;
	}

	@Override
	public void draw(GL10 gl) {
		super.draw(gl);

		float h = TonguePiece.HEIGHT * maze.getScaleFactor();
		float w = TonguePiece.WIDTH * maze.getScaleFactor();

		for (TonguePiece tp : pieces) {
			gl.glPushMatrix();

			float ux = -WIDTH / 2.0f + tp.j * w;
			float uy = HEIGHT / 2.0f - tp.i * h;
			float x = ux + w / 2.0f;
			float y = uy - h / 2.0f;

			gl.glTranslatef(x, y, 0);
			gl.glScalef(maze.getScaleFactor(), maze.getScaleFactor(),
					maze.getScaleFactor());

			tp.draw(gl);

			gl.glPopMatrix();
		}
	}

	public void move(float x, float y) {
		float h = TonguePiece.HEIGHT * maze.getScaleFactor();
		float w = TonguePiece.WIDTH * maze.getScaleFactor();

		short j = (short) ((x + WIDTH / 2.0f) / w);
		short i = (short) ((y - HEIGHT / 2.0f) / -h);

		do {
			short j0 = pieces.element().j;
			short i0 = pieces.element().i;

			boolean top = i < i0 && (maze.getGraph()[i0][j0] & Tunnel.TOP) > 0;
			boolean bottom = i > i0
					&& (maze.getGraph()[i0][j0] & Tunnel.BOTTOM) > 0;
			boolean right = j > j0
					&& (maze.getGraph()[i0][j0] & Tunnel.RIGHT) > 0;
			boolean left = j < j0
					&& (maze.getGraph()[i0][j0] & Tunnel.LEFT) > 0;

			if (pieces.elementAt(-2) != null) {
				short j00 = pieces.elementAt(-2).j;
				short i00 = pieces.elementAt(-2).i;

				top &= i0 <= i00;
				bottom &= i0 >= i00;
				right &= j0 >= j00;
				left &= j0 <= j00;
			}

			float d = 0f;
			short dir = 0;

			if (top && i0 - i > d) {
				d = i0 - i;
				dir = Tunnel.TOP;
			}
			if (bottom && i - i0 > d) {
				d = i - i0;
				dir = Tunnel.BOTTOM;
			}
			if (right && j - j0 > d) {
				d = j - j0;
				dir = Tunnel.RIGHT;
			}
			if (left && j0 - j > d) {
				d = j0 - j;
				dir = Tunnel.LEFT;
			}

			if (dir == 0) {
				break;
			}

			if (dir == Tunnel.TOP) {
				pieces.element().setOrientation((short) (pieces.element().orientation | Tunnel.TOP));
				pieces.push(new TonguePiece((short) (i0 - 1), j0, Tunnel.BOTTOM));
			} else if (dir == Tunnel.BOTTOM) {
				pieces.element().setOrientation((short) (pieces.element().orientation | Tunnel.BOTTOM));
				pieces.push(new TonguePiece((short) (i0 + 1), j0, Tunnel.TOP));
			}
			if (dir == Tunnel.RIGHT) {
				pieces.element().setOrientation((short) (pieces.element().orientation | Tunnel.RIGHT));
				pieces.push(new TonguePiece(i0, (short) (j0 + 1), Tunnel.LEFT));
			} else if (dir == Tunnel.LEFT) {
				pieces.element().setOrientation((short) (pieces.element().orientation | Tunnel.LEFT));
				pieces.push(new TonguePiece(i0, (short) (j0 - 1), Tunnel.RIGHT));
			}

			if (hasCollision(pieces.element().i, pieces.element().j)) {
				collisionListener
						.notify(pieces.element().i, pieces.element().j);
				break;
			}

			moveListener.notify(pieces.element().i, pieces.element().j);
		} while (true);
	}

	public boolean release(List<Ant> deadAnts) {
		if (this.deadAnts == null) {
			this.deadAnts = new LinkedList<Pair<Integer, Ant>>();
			for (Ant ant : deadAnts) {
				for (int j = 0; j < pieces.size(); j++) {
					if (ant.getI() == pieces.elementAt(j).i
							&& ant.getJ() == pieces.elementAt(j).j) {
						this.deadAnts.add(new Pair<Integer, Ant>(j, ant));
						break;
					}
				}
			}
		}

		List<Object> toRemove = new ArrayList<Object>();
		List<Pair<Integer, Ant>> toAdd = new ArrayList<Pair<Integer, Ant>>();
		for (Pair<Integer, Ant> walker : this.deadAnts) {
			toRemove.add(walker);

			if (walker.first < 1) {
				score++;
				deadAnts.remove(walker.second);
				continue;
			}

			walker.second.setI(pieces.elementAt(walker.first - 1).i);
			walker.second.setJ(pieces.elementAt(walker.first - 1).j);
			toAdd.add(new Pair<Integer, Ant>(walker.first - 1, walker.second));
		}

		this.deadAnts.removeAll(toRemove);
		this.deadAnts.addAll(toAdd);

		for (int i = 0; !pieces.isEmpty() && i < 1; i++) {
			pieces.pop();
		}
		return pieces.isEmpty();
	}

	public short getScore() {
		return score;
	}

	public boolean hasCollision(short i, short j) {
		for (TonguePiece piece : pieces) {
			if (!piece.equals(pieces.element()) && i == piece.i && j == piece.j) {
				return true;
			}
		}
		return false;
	}
}
